We have used SingleR and scANVI/scArches to perform cell type annotation on SCPCP000004 samples using the NBAtlas reference. The goal of this notebook is to compare these two sets of inferences to determine if they generally agree.

Setup

suppressPackageStartupMessages({
  library(ggplot2)
  library(patchwork)
  library(SingleCellExperiment)
})

theme_set(theme_bw())
umap_theme <- list(
  theme(
    aspect.ratio = 1,
    legend.position = "bottom", 
    axis.text = element_blank(), 
    axis.ticks = element_blank()
  ) 
)
set.seed(2025) # used for some viz

# Define color ramp for shared use in the heatmaps
heatmap_col_fun <- circlize::colorRamp2(c(0, 1), colors = c("white", "darkslateblue"))
# Set heatmap padding option
ComplexHeatmap::ht_opt(TITLE_PADDING = grid::unit(0.6, "in"))

# settings
options(
  dplyr.summarise.inform = FALSE, 
  readr.show_col_types = FALSE
)

Paths

# The base path for the OpenScPCA repository, found by its (hidden) .git directory
repository_base <- rprojroot::find_root(rprojroot::is_git_root)

data_dir <- file.path(repository_base, "data", "current", "SCPCP000004")
merged_dir <- file.path(repository_base, "data", "current", "results", "merge-sce", "SCPCP000004")
module_dir <- file.path(repository_base, "analyses", "cell-type-neuroblastoma-04")
ref_dir <- file.path(module_dir, "references")
results_dir <- file.path(module_dir, "results")
singler_dir <- file.path(results_dir, "singler")
scanvi_dir <- file.path(results_dir, "scanvi")
# merged SCE file
sce_file <- file.path(
  merged_dir,
  "SCPCP000004_merged.rds"
)

# SingleR files
singler_files <- list.files(
  path = singler_dir,
  pattern = "_singler-annotations\\.tsv",
  recursive = TRUE,
  full.names = TRUE
) |>
  # add names as library id
  purrr::set_names(
    \(x) {
      stringr::str_remove(basename(x), "_singler-annotations.tsv")
    }
  )

# scanvi predictions
scanvi_file <- file.path(
  scanvi_dir, 
  "scanvi-predictions.tsv"
)

# broad consensus cell type groups
validation_url <- "https://raw.githubusercontent.com/AlexsLemonade/OpenScPCA-analysis/refs/heads/main/analyses/cell-type-consensus/references/consensus-validation-groups.tsv"

# palette files
recoded_palette_file <- file.path(
  module_dir,
  "palettes",
  "harmonized-cell-type-palette.tsv"
)
nbatlas_palette_file <- file.path(
  module_dir,
  "palettes",
  "nbatlas-cell-type-palette.tsv"
)

# label mapping
nbatlas_label_map_file <- file.path(ref_dir, "nbatlas-label-map.tsv")

# sample metadata
sample_metadata_file <- file.path(data_dir, "single_cell_metadata.tsv")

Functions

# Source Jaccard and heatmap utilities functions
source(file.path(module_dir, "scripts", "utils", "jaccard-utils.R"))

# Source additional utilities functions:
# - subset_nbatlas_markers()
# - harmonize_celltypes()
# - faceted_umap()
# - generate_dotplot()
source(file.path(module_dir, "scripts", "utils", "celltype-utils.R"))

Read and prepare input data

Read merged SCE object:

merged_sce <- readRDS(sce_file)
# immediately remove assays we don't need for space
assay(merged_sce, "spliced") <- NULL
assay(merged_sce, "counts") <- NULL
reducedDim(merged_sce, "PCA") <- NULL

Read SingleR results:

singler_df <- singler_files |>
  purrr::map(readr::read_tsv) |>
  purrr::list_rbind(names_to = "library_id") |>
  dplyr::mutate(
    # add cell id column for unique rows & joining
    cell_id = glue::glue("{library_id}-{barcodes}"),
    # recode NA -> "unknown_singler"
    singler = ifelse(is.na(pruned.labels),"Unknown", pruned.labels)
  ) |>
  dplyr::select(cell_id, singler, singler_delta_next = delta.next)

Read and prepare scanvi results:

scanvi_full_df <- readr::read_tsv(
  file.path(scanvi_dir, "scanvi_predictions.tsv")
) |>
  dplyr::select(
    cell_id, 
    scanvi = scanvi_prediction, 
    starts_with("pp_")
  ) |>
  tidyr::pivot_longer(
    starts_with("pp_"), 
    names_to = "posterior_celltype", 
    values_to = "posterior"
  ) |>
  dplyr::mutate(posterior_celltype = stringr::str_remove(posterior_celltype, "^pp_"))

# create dedicated column for the posterior associated with the label
scanvi_df <- scanvi_full_df |>
  dplyr::filter(scanvi == posterior_celltype) |>
  dplyr::select(cell_id, scanvi, scanvi_posterior = posterior)

Read and prepare additional helper files for viz:

# validation groups data frame
validation_df <- readr::read_tsv(validation_url) |>
  dplyr::select(consensus_annotation, validation_group_annotation)

# set up palettes
recoded_palette_df <- readr::read_tsv(recoded_palette_file)
recoded_celltype_pal <- recoded_palette_df$hex_color
names(recoded_celltype_pal) <- recoded_palette_df$harmonized_label
# for this notebook, we want just a single Unknown
recoded_celltype_pal["Unknown"] <- recoded_celltype_pal["unknown_singler"]


nbatlas_palette_df <- readr::read_tsv(nbatlas_palette_file)
nbatlas_celltype_pal <- nbatlas_palette_df$hex_color
names(nbatlas_celltype_pal) <- nbatlas_palette_df$NBAtlas_label

# sample metadata
sample_metadata <- readr::read_tsv(sample_metadata_file)

# label mapping
nbatlas_label_map <- readr::read_tsv(nbatlas_label_map_file)

scANVI posterior probabilities

Before comparing to SingleR, we should get a sense of reliability for the scANVI results. scANVI returns a cell-specific posterior probability for each label, so we’ll begin by looking at these distributions.

First, what is the distribution of posterior probabilities for assigned labels? We’ll make a histogram below using a very visible color to ensure the plot is legible, where each panel is a library, and libraries are ordered by size.

scanvi_df |>
  tidyr::separate(cell_id, into = c("library_id", "barcode")) |>
  dplyr::add_count(library_id) |>
  dplyr::mutate(
    library_id = glue::glue("{library_id} (N={n})"),
    library_id = factor(library_id), 
    library_id = forcats::fct_infreq(library_id), 
    library_id = forcats::fct_rev(library_id)
  ) |>
  ############ into ggplot ################
  ggplot() + 
  aes(x = scanvi_posterior) + 
  # ugly but _very visible_ color
  geom_histogram(bins = 100, fill = "magenta", color = "magenta") + 
  facet_wrap(vars(library_id), scales = "free_y", ncol = 7)

Across libraries, the median posterior probability is always exceptionally high which suggests scANVI confidence is roughly the same across libraries.

Let’s instead look at these probabilities on a per-cell type basis, this time with a ridge plot including a line for the median value for each distribution:

scanvi_ridgeplot <- scanvi_df |>
  dplyr::group_by(scanvi) |>
  dplyr::mutate(scanvi = glue::glue("{scanvi} (n={dplyr::n()})")) |>
  ########### into ggplot ############
  ggplot() + 
    aes(y = forcats::fct_infreq(scanvi), x = scanvi_posterior) + 
    # show the median
    ggridges::stat_density_ridges(quantile_lines = TRUE, quantiles = 2, alpha = 0.7) +
    labs(x = "posterior probability", y = "scANVI label (number of cells)") 
scanvi_ridgeplot

While annotation reliability is about the same on a per-library basis, it seems that certain cell types may be much easier to classify than others which may be a factor of how well represented they are in the NBAtlas reference. There are very few cells in general associated with the cell types that have very low median posterior probabilities (NKT cell, Migratory cDC, Circulating NK cell).

A 0.75 threshold seems like it may be a good middle-ground for preserving labels while removing low-confidence annotations. What fraction of cells will this remove?

sum(scanvi_df$scanvi_posterior < 0.75) / nrow(scanvi_df)
[1] 0.01886238

What are those cell types?

scanvi_df |>
  dplyr::filter(scanvi_posterior < 0.75) |>
  dplyr::count(scanvi) |>
  dplyr::arrange(desc(n))

Notably, none are Neuroendocrine (the tumor type).

We’ll apply the 0.75 threshold below by recoding these cells to Unknown.

pp_threshold <- 0.75
scanvi_df <- scanvi_df |>
  dplyr::mutate(scanvi = ifelse(
    scanvi_posterior < pp_threshold, 
    "Unknown",
    scanvi
  ))

Compare labels

We’ll create a single data frame with UMAP coordinates and all cell types (consensus, SingleR, and scANVI). We’ll do this at two levels of organization:

celltype_df <- scuttle::makePerCellDF(
  merged_sce,
  use.coldata = c("sample_id", "is_xenograft", "consensus_celltype_annotation"),
  use.dimred = c("UMAP")
) |>
  # there ARE repeated barcodes so we need to keep cell_id around
  tibble::rownames_to_column("cell_id") |>
  dplyr::rename(
    UMAP1 = UMAP.1,
    UMAP2 = UMAP.2,
    consensus_annotation = consensus_celltype_annotation
  ) |>
  dplyr::left_join(validation_df, by = "consensus_annotation") |>
  # recode consensus NAs to "Unknown"
  dplyr::mutate(
    validation_group_annotation = ifelse(
      is.na(validation_group_annotation), "Unknown", validation_group_annotation
    )
  ) |>
  dplyr::select(-consensus_annotation) |>
  # merge in the annotations
  dplyr::left_join(singler_df, by = "cell_id") |>
  dplyr::left_join(scanvi_df, by = "cell_id") |>
  # make it tidy
  tidyr::pivot_longer(
    c(validation_group_annotation, singler, scanvi), 
    names_to = "celltype_method", 
    values_to = "label"
  ) |>
  harmonize_celltypes(label, label_recoded) |>
  dplyr::mutate(label = ifelse(label == "NE", "Neuroendocrine", label)) |>
  tidyr::separate(cell_id, into = c("library_id", "barcode"), remove = FALSE)

UMAP

This section displays the UMAP for the merged object (not integrated) with cell type labels across methods. We show a panel for each cell type method.

SingleR vs scANVI

This set of UMAPs shows annotation results from SingleR and scANVI.

Since there are many cell types in this plot, it has been colored so that all cell types in a given “family” each share a color, where we consider these family groupings:

  • T cells
  • Natural killer cells
  • Conventional dendritic cells
  • Monocytes
label_order <- nbatlas_label_map |>
  dplyr::add_count(NBAtlas_family) |>
  dplyr::arrange(desc(n), NBAtlas_family) |>
  dplyr::pull(NBAtlas_label)
label_order <- c(label_order, "Unknown")

umap_direct_df <- celltype_df |>
  dplyr::filter(celltype_method != "validation_group_annotation") |>
  # shuffle points to ensure order they are plotted is random
  dplyr::sample_frac(1) |> 
  # order celltypes so that families are grouped together for a cleaner legend
  # note this will give warnings if any of those cells aren't in the annotation, but that's fine
  dplyr::mutate(label = factor(label, levels = label_order))
  
ggplot(umap_direct_df) +
  aes(x = UMAP1, y = UMAP2, color = label) +
  geom_point(alpha = 0.5, size = 0.2) +
  scale_color_manual(values = nbatlas_celltype_pal) +
  facet_wrap(vars(celltype_method)) +
  umap_theme +
  guides(color = guide_legend(override.aes = list(size = 2, alpha = 1)))

Cell type annotations look broadly consistent between these methods, although SingleR appears to assign more heterogeneity within the “clumps” of cells (which likely correspond to individual libraries in this merged object).

Harmonized labels for consensus comparison

This set of UMAPs harmonizes cell type labels into broader groupings to enable a comparison with consensus annotations. Note that the Unknown cell type refers to cells uncategorized by SingleR or scANVI.

ggplot(celltype_df) +
  aes(x = UMAP1, y = UMAP2, color = label_recoded) +
  geom_point(alpha = 0.5, size = 0.2) +
  scale_color_manual(values = recoded_celltype_pal) +
  facet_wrap(vars(celltype_method)) +
  umap_theme +
  guides(color = guide_legend(override.aes = list(size = 2, alpha = 1)))

We see broad compatibility among all three approaches, and the Unknown consensus indeed appear to be mostly tumor cells (here, Neuroendocrine). This is reassuring that we have some robust cell type identifications here!

Heatmap

This section shows heatmaps comparing annotations to one another.

SingleR vs scANVI

This heatmap directly compares SingleR and scANVI annotations.

# make a wide version for the heatmap
celltype_wide_df <- celltype_df |>
  dplyr::filter(celltype_method != "validation_group_annotation") |>
  dplyr::select(-label_recoded) |>
  tidyr::pivot_wider(
    names_from = celltype_method,
    values_from = label
  )

make_jaccard_heatmap(
  celltype_wide_df,
  "scanvi",
  "singler",
  "scANVI label",
  "SingleR label"
)

This heatmap shows excellent correspondence between SingleR and scANVI annotations with most of the weight along the diagonal.

We’ll nail this down a bit further with some confusion matrix metrics:

# ensure factors are compatible for caret
all_levels <- c(
  celltype_wide_df$scanvi, 
  celltype_wide_df$singler
) |> unique()

caret::confusionMatrix(
  factor(celltype_wide_df$scanvi, levels = all_levels),  
  factor(celltype_wide_df$singler, levels = all_levels)
)$overall
      Accuracy          Kappa  AccuracyLower  AccuracyUpper   AccuracyNull AccuracyPValue  McnemarPValue 
     0.8372332      0.6317526      0.8355528      0.8389031      0.6887921      0.0000000            NaN 

Harmonized labels for consensus comparison

This stacked heatmap compares all three methods to one another, using broader cell type groupings to enable a more direct comparison with consensus annotations.

# make a wide version for the heatmap
celltype_recoded_wide_df <- celltype_df |>
  dplyr::select(-label) |>
  tidyr::pivot_wider(
    names_from = celltype_method,
    values_from = label_recoded
  )

list(
  "SingleR annotations" = make_jaccard_matrix(celltype_recoded_wide_df, "validation_group_annotation", "singler"),
  "scANVI annotations" = make_jaccard_matrix(celltype_recoded_wide_df, "validation_group_annotation", "scanvi")
) |>
  purrr::imap(
    \(jaccard_mat, name) create_single_heatmap(
        mat = jaccard_mat,
        row_title = name,
        column_title = "Consensus validation groups",
        keep_legend_name = "SingleR annotations" # arbitrary for labeling
    )
  ) |>
  # concatenate vertically into HeatmapList object
  purrr::reduce(ComplexHeatmap::`%v%`) |>
  ComplexHeatmap::draw(heatmap_legend_side = "right")

Again, we see excellent correspondence among all three methods.

Cross-library comparison

Across libraries, what fraction of SingleR and scANVI labels exactly agree? For this, we’ll compare among:

The table below shows these fractions for each library along with the library size. We also visualize the relationship between library size and family-based agreement.

# celltype_wide_df uses the original labels
identical_agree_df <- celltype_wide_df |>
  dplyr::group_by(library_id) |>
  dplyr::summarize(
    frac_identical = sum(scanvi == singler)/dplyr::n(), 
    library_size = dplyr::n()
  )

# create a wide data frame with "family" labels where possible
celltype_family_df <- celltype_df |>
  dplyr::filter(celltype_method != "validation_group_annotation") |>
  dplyr::left_join(
    nbatlas_label_map, by = c("label" = "NBAtlas_label")
  ) |>
  dplyr::select(cell_id, library_id, celltype_method, label) |>
  tidyr::pivot_wider(
    names_from = celltype_method,
    values_from = label
  )

family_agree_df <- celltype_family_df |>
  dplyr::group_by(library_id) |>
  dplyr::summarize(frac_same_family = sum(scanvi == singler)/dplyr::n())

# if families disagree, then there is no agreement at all
disagree_df <- celltype_family_df |>
  dplyr::group_by(library_id) |>
  dplyr::summarize(frac_disagree = sum(scanvi != singler)/dplyr::n())
# combine into a single table for display & viz
frac_df <- identical_agree_df |>
  dplyr::left_join(family_agree_df) |>
  dplyr::left_join(disagree_df) |>
  # round to 4 decimals
  dplyr::mutate(dplyr::across(contains("frac_"), \(x) round(x, 4))) |>
  dplyr::select(
    library_id, 
    library_size, 
    frac_identical, 
    frac_same_family, 
    frac_disagree
  ) |>
  dplyr::arrange(library_size)

# use DT for improved exploration ability
DT::datatable(frac_df, rownames = FALSE)
# add PDX column for plotting
frac_df <- celltype_df |> 
  dplyr::select(library_id, is_xenograft) |>
  unique() |>
  dplyr::right_join(frac_df)

ggplot(frac_df) + 
  aes(x = library_size, y = frac_same_family) + 
  geom_point(aes(color = is_xenograft)) + 
  geom_smooth(method = "lm") +
  labs(
    x = "Library size",
    y = "Fraction of SingleR/scANVI labels\nfrom the same family"
  ) +
  # stop going above 1
  scale_y_continuous(limits = c(0, 1))

Most cells have agreeing annotations in the majority of libraries. Considering closely-related cells in the same family usually provides a couple more cells to be annotated, but not a large percentage. There appears to be a slight trend (not significant) that larger libraries have more agreement between methods, but this also seems driven by the libraries with low correspondence.

Libraries with high levels of disagreement

There are four libraries (2 PDX and 2 tissue) which show less than 50% agreement, so we should look at these a bit more closely. Notably, these do not correspond to the four smallest libraries.

Here is the sample metadata for these four samples:

disagree_ids <- frac_df |>
  dplyr::filter(frac_disagree >= 0.5) |>
  dplyr::pull(library_id)

disagree_metadata <- sample_metadata |>
  dplyr::filter(scpca_library_id %in% disagree_ids)

Looking closely here, we can see that these samples were in fact derived from two participants where one sample is a xenograft and the other is tissue.

disagree_metadata |>
  dplyr::select(scpca_library_id, sample_type, participant_id) |>
  dplyr::arrange(participant_id)

These participant IDs are not associated with any other samples:

sample_metadata |>
  dplyr::filter(participant_id %in% disagree_metadata$participant_id) |>
  dplyr::filter(!(scpca_library_id) %in% disagree_ids)

It’s possible that patient-specific biology explains the discrepancies we see for these samples’ cell types.

We’ll look more closely at their disagreements as well with heatmaps:

disagree_ids |>
  # walk to only print the heatmaps once
  purrr::walk(
    \(id) {
      library_df <- celltype_wide_df |>
        dplyr::filter(library_id == id)
      
     frac_disagree <- frac_df |>
       dplyr::filter(library_id == id) |>
       dplyr::pull(frac_disagree)
       
     
     heatmap_title <- glue::glue("{id} (fraction disagree = {frac_disagree})")
      
      make_jaccard_heatmap(
        library_df,
        "scanvi",
        "singler",
        glue::glue("{heatmap_title}\n\nscANVI label"),
        "SingleR label"
      )
  })

For these libraries, the main source of disagreement is scANVI applying more homogeneous labeling of Neuroendocrine compared to SingleR which is assigning more varied cell types. Note that the two libraries where scANVI is only detecting Neuroendocrine cells are the PDX libraries, for which we expect lower cell type diversity in general. It’s not unlikely that a lot of what SingleR has labeled Stromal other or Schwann may be tumor.

Conclusions

Overall SingleR and scANVI appear to have made highly similar predictions for the vast majority of libraries. These cell types are also fairly similar to our existing consensus cell types. Therefore, we can use agreement between SingleR and scANVI to derive final annotations for this analysis module.

sessionInfo()
R version 4.4.0 (2024-04-24)
Platform: aarch64-apple-darwin20
Running under: macOS 15.6

Matrix products: default
BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: America/New_York
tzcode source: internal

attached base packages:
[1] stats4    stats     graphics  grDevices datasets  utils     methods   base     

other attached packages:
 [1] SingleCellExperiment_1.26.0 SummarizedExperiment_1.34.0 Biobase_2.64.0              GenomicRanges_1.56.2       
 [5] GenomeInfoDb_1.40.1         IRanges_2.38.1              S4Vectors_0.42.1            BiocGenerics_0.50.0        
 [9] MatrixGenerics_1.16.0       matrixStats_1.5.0           patchwork_1.3.0             ggplot2_3.5.2              

loaded via a namespace (and not attached):
  [1] RColorBrewer_1.1-3        jsonlite_2.0.0            shape_1.4.6.1             magrittr_2.0.3            farver_2.1.2             
  [6] GlobalOptions_0.1.2       zlibbioc_1.50.0           vctrs_0.6.5               Cairo_1.6-2               DelayedMatrixStats_1.26.0
 [11] htmltools_0.5.8.1         S4Arrays_1.4.1            forcats_1.0.0             curl_6.3.0                SparseArray_1.4.8        
 [16] pROC_1.18.5               caret_7.0-1               sass_0.4.10               parallelly_1.45.0         bslib_0.9.0              
 [21] htmlwidgets_1.6.4         plyr_1.8.9                lubridate_1.9.4           cachem_1.1.0              lifecycle_1.0.4          
 [26] iterators_1.0.14          pkgconfig_2.0.3           Matrix_1.7-1              R6_2.6.1                  fastmap_1.2.0            
 [31] GenomeInfoDbData_1.2.12   future_1.58.0             clue_0.3-66               digest_0.6.37             colorspace_2.1-1         
 [36] rprojroot_2.0.4           crosstalk_1.2.1           beachmat_2.20.0           labeling_0.4.3            timechange_0.3.0         
 [41] mgcv_1.9-1                httr_1.4.7                abind_1.4-8               compiler_4.4.0            proxy_0.4-27             
 [46] bit64_4.6.0-1             withr_3.0.2               doParallel_1.0.17         BiocParallel_1.38.0       MASS_7.3-64              
 [51] lava_1.8.1                DelayedArray_0.30.1       rjson_0.2.23              ModelMetrics_1.2.2.2      tools_4.4.0              
 [56] future.apply_1.20.0       nnet_7.3-20               glue_1.8.0                nlme_3.1-166              grid_4.4.0               
 [61] cluster_2.1.8             reshape2_1.4.4            generics_0.1.4            recipes_1.3.1             gtable_0.3.6             
 [66] tzdb_0.5.0                class_7.3-23              tidyr_1.3.1               data.table_1.17.4         hms_1.1.3                
 [71] XVector_0.44.0            foreach_1.5.2             pillar_1.10.2             stringr_1.5.1             vroom_1.6.5              
 [76] circlize_0.4.16           splines_4.4.0             dplyr_1.1.4               lattice_0.22-6            renv_1.1.3               
 [81] survival_3.8-3            bit_4.6.0                 tidyselect_1.2.1          ComplexHeatmap_2.20.0     scuttle_1.14.0           
 [86] knitr_1.50                xfun_0.52                 hardhat_1.4.1             timeDate_4041.110         DT_0.33                  
 [91] stringi_1.8.7             UCSC.utils_1.0.0          yaml_2.3.10               evaluate_1.0.3            codetools_0.2-20         
 [96] tibble_3.3.0              BiocManager_1.30.26       cli_3.6.5                 rpart_4.1.23              jquerylib_0.1.4          
[101] Rcpp_1.0.14               globals_0.18.0            png_0.1-8                 parallel_4.4.0            gower_1.0.2              
[106] readr_2.1.5               sparseMatrixStats_1.16.0  listenv_0.9.1             ipred_0.9-15              scales_1.4.0             
[111] prodlim_2025.04.28        ggridges_0.5.6            e1071_1.7-16              purrr_1.0.4               crayon_1.5.3             
[116] GetoptLong_1.0.5          rlang_1.1.6              
LS0tCnRpdGxlOiAiQ29tcGFyZSBzY0FOVkkgYW5kIFNpbmdsZVIgYW5ub3RhdGlvbnMiCmF1dGhvcjogIlN0ZXBoYW5pZSBKLiBTcGllbG1hbiIKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKLS0tCgpXZSBoYXZlIHVzZWQgYFNpbmdsZVJgIGFuZCBgc2NBTlZJL3NjQXJjaGVzYCB0byBwZXJmb3JtIGNlbGwgdHlwZSBhbm5vdGF0aW9uIG9uIGBTQ1BDUDAwMDAwNGAgc2FtcGxlcyB1c2luZyB0aGUgYE5CQXRsYXNgIHJlZmVyZW5jZS4KVGhlIGdvYWwgb2YgdGhpcyBub3RlYm9vayBpcyB0byBjb21wYXJlIHRoZXNlIHR3byBzZXRzIG9mIGluZmVyZW5jZXMgdG8gZGV0ZXJtaW5lIGlmIHRoZXkgZ2VuZXJhbGx5IGFncmVlLgogICAKIyMgU2V0dXAKCmBgYHtyLCB3YXJuaW5nID0gRkFMU0V9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7CiAgbGlicmFyeShnZ3Bsb3QyKQogIGxpYnJhcnkocGF0Y2h3b3JrKQogIGxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCn0pCgp0aGVtZV9zZXQodGhlbWVfYncoKSkKdW1hcF90aGVtZSA8LSBsaXN0KAogIHRoZW1lKAogICAgYXNwZWN0LnJhdGlvID0gMSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLCAKICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpCiAgKSAKKQpzZXQuc2VlZCgyMDI1KSAjIHVzZWQgZm9yIHNvbWUgdml6CgojIERlZmluZSBjb2xvciByYW1wIGZvciBzaGFyZWQgdXNlIGluIHRoZSBoZWF0bWFwcwpoZWF0bWFwX2NvbF9mdW4gPC0gY2lyY2xpemU6OmNvbG9yUmFtcDIoYygwLCAxKSwgY29sb3JzID0gYygid2hpdGUiLCAiZGFya3NsYXRlYmx1ZSIpKQojIFNldCBoZWF0bWFwIHBhZGRpbmcgb3B0aW9uCkNvbXBsZXhIZWF0bWFwOjpodF9vcHQoVElUTEVfUEFERElORyA9IGdyaWQ6OnVuaXQoMC42LCAiaW4iKSkKCiMgc2V0dGluZ3MKb3B0aW9ucygKICBkcGx5ci5zdW1tYXJpc2UuaW5mb3JtID0gRkFMU0UsIAogIHJlYWRyLnNob3dfY29sX3R5cGVzID0gRkFMU0UKKQoKCmBgYAoKCiMjIyBQYXRocwoKYGBge3IgYmFzZSBwYXRoc30KIyBUaGUgYmFzZSBwYXRoIGZvciB0aGUgT3BlblNjUENBIHJlcG9zaXRvcnksIGZvdW5kIGJ5IGl0cyAoaGlkZGVuKSAuZ2l0IGRpcmVjdG9yeQpyZXBvc2l0b3J5X2Jhc2UgPC0gcnByb2pyb290OjpmaW5kX3Jvb3QocnByb2pyb290Ojppc19naXRfcm9vdCkKCmRhdGFfZGlyIDwtIGZpbGUucGF0aChyZXBvc2l0b3J5X2Jhc2UsICJkYXRhIiwgImN1cnJlbnQiLCAiU0NQQ1AwMDAwMDQiKQptZXJnZWRfZGlyIDwtIGZpbGUucGF0aChyZXBvc2l0b3J5X2Jhc2UsICJkYXRhIiwgImN1cnJlbnQiLCAicmVzdWx0cyIsICJtZXJnZS1zY2UiLCAiU0NQQ1AwMDAwMDQiKQptb2R1bGVfZGlyIDwtIGZpbGUucGF0aChyZXBvc2l0b3J5X2Jhc2UsICJhbmFseXNlcyIsICJjZWxsLXR5cGUtbmV1cm9ibGFzdG9tYS0wNCIpCnJlZl9kaXIgPC0gZmlsZS5wYXRoKG1vZHVsZV9kaXIsICJyZWZlcmVuY2VzIikKcmVzdWx0c19kaXIgPC0gZmlsZS5wYXRoKG1vZHVsZV9kaXIsICJyZXN1bHRzIikKc2luZ2xlcl9kaXIgPC0gZmlsZS5wYXRoKHJlc3VsdHNfZGlyLCAic2luZ2xlciIpCnNjYW52aV9kaXIgPC0gZmlsZS5wYXRoKHJlc3VsdHNfZGlyLCAic2NhbnZpIikKYGBgCgoKYGBge3IgZmlsZSBwYXRoc30KIyBtZXJnZWQgU0NFIGZpbGUKc2NlX2ZpbGUgPC0gZmlsZS5wYXRoKAogIG1lcmdlZF9kaXIsCiAgIlNDUENQMDAwMDA0X21lcmdlZC5yZHMiCikKCiMgU2luZ2xlUiBmaWxlcwpzaW5nbGVyX2ZpbGVzIDwtIGxpc3QuZmlsZXMoCiAgcGF0aCA9IHNpbmdsZXJfZGlyLAogIHBhdHRlcm4gPSAiX3NpbmdsZXItYW5ub3RhdGlvbnNcXC50c3YiLAogIHJlY3Vyc2l2ZSA9IFRSVUUsCiAgZnVsbC5uYW1lcyA9IFRSVUUKKSB8PgogICMgYWRkIG5hbWVzIGFzIGxpYnJhcnkgaWQKICBwdXJycjo6c2V0X25hbWVzKAogICAgXCh4KSB7CiAgICAgIHN0cmluZ3I6OnN0cl9yZW1vdmUoYmFzZW5hbWUoeCksICJfc2luZ2xlci1hbm5vdGF0aW9ucy50c3YiKQogICAgfQogICkKCiMgc2NhbnZpIHByZWRpY3Rpb25zCnNjYW52aV9maWxlIDwtIGZpbGUucGF0aCgKICBzY2FudmlfZGlyLCAKICAic2NhbnZpLXByZWRpY3Rpb25zLnRzdiIKKQoKIyBicm9hZCBjb25zZW5zdXMgY2VsbCB0eXBlIGdyb3Vwcwp2YWxpZGF0aW9uX3VybCA8LSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL0FsZXhzTGVtb25hZGUvT3BlblNjUENBLWFuYWx5c2lzL3JlZnMvaGVhZHMvbWFpbi9hbmFseXNlcy9jZWxsLXR5cGUtY29uc2Vuc3VzL3JlZmVyZW5jZXMvY29uc2Vuc3VzLXZhbGlkYXRpb24tZ3JvdXBzLnRzdiIKCiMgcGFsZXR0ZSBmaWxlcwpyZWNvZGVkX3BhbGV0dGVfZmlsZSA8LSBmaWxlLnBhdGgoCiAgbW9kdWxlX2RpciwKICAicGFsZXR0ZXMiLAogICJoYXJtb25pemVkLWNlbGwtdHlwZS1wYWxldHRlLnRzdiIKKQpuYmF0bGFzX3BhbGV0dGVfZmlsZSA8LSBmaWxlLnBhdGgoCiAgbW9kdWxlX2RpciwKICAicGFsZXR0ZXMiLAogICJuYmF0bGFzLWNlbGwtdHlwZS1wYWxldHRlLnRzdiIKKQoKIyBsYWJlbCBtYXBwaW5nCm5iYXRsYXNfbGFiZWxfbWFwX2ZpbGUgPC0gZmlsZS5wYXRoKHJlZl9kaXIsICJuYmF0bGFzLWxhYmVsLW1hcC50c3YiKQoKIyBzYW1wbGUgbWV0YWRhdGEKc2FtcGxlX21ldGFkYXRhX2ZpbGUgPC0gZmlsZS5wYXRoKGRhdGFfZGlyLCAic2luZ2xlX2NlbGxfbWV0YWRhdGEudHN2IikKYGBgCgojIyMgRnVuY3Rpb25zCgpgYGB7cn0KIyBTb3VyY2UgSmFjY2FyZCBhbmQgaGVhdG1hcCB1dGlsaXRpZXMgZnVuY3Rpb25zCnNvdXJjZShmaWxlLnBhdGgobW9kdWxlX2RpciwgInNjcmlwdHMiLCAidXRpbHMiLCAiamFjY2FyZC11dGlscy5SIikpCgojIFNvdXJjZSBhZGRpdGlvbmFsIHV0aWxpdGllcyBmdW5jdGlvbnM6CiMgLSBzdWJzZXRfbmJhdGxhc19tYXJrZXJzKCkKIyAtIGhhcm1vbml6ZV9jZWxsdHlwZXMoKQojIC0gZmFjZXRlZF91bWFwKCkKIyAtIGdlbmVyYXRlX2RvdHBsb3QoKQpzb3VyY2UoZmlsZS5wYXRoKG1vZHVsZV9kaXIsICJzY3JpcHRzIiwgInV0aWxzIiwgImNlbGx0eXBlLXV0aWxzLlIiKSkKYGBgCgojIyMgUmVhZCBhbmQgcHJlcGFyZSBpbnB1dCBkYXRhCgpSZWFkIG1lcmdlZCBTQ0Ugb2JqZWN0OgoKYGBge3J9Cm1lcmdlZF9zY2UgPC0gcmVhZFJEUyhzY2VfZmlsZSkKIyBpbW1lZGlhdGVseSByZW1vdmUgYXNzYXlzIHdlIGRvbid0IG5lZWQgZm9yIHNwYWNlCmFzc2F5KG1lcmdlZF9zY2UsICJzcGxpY2VkIikgPC0gTlVMTAphc3NheShtZXJnZWRfc2NlLCAiY291bnRzIikgPC0gTlVMTApyZWR1Y2VkRGltKG1lcmdlZF9zY2UsICJQQ0EiKSA8LSBOVUxMCmBgYAoKUmVhZCBTaW5nbGVSIHJlc3VsdHM6CgpgYGB7cn0Kc2luZ2xlcl9kZiA8LSBzaW5nbGVyX2ZpbGVzIHw+CiAgcHVycnI6Om1hcChyZWFkcjo6cmVhZF90c3YpIHw+CiAgcHVycnI6Omxpc3RfcmJpbmQobmFtZXNfdG8gPSAibGlicmFyeV9pZCIpIHw+CiAgZHBseXI6Om11dGF0ZSgKICAgICMgYWRkIGNlbGwgaWQgY29sdW1uIGZvciB1bmlxdWUgcm93cyAmIGpvaW5pbmcKICAgIGNlbGxfaWQgPSBnbHVlOjpnbHVlKCJ7bGlicmFyeV9pZH0te2JhcmNvZGVzfSIpLAogICAgIyByZWNvZGUgTkEgLT4gInVua25vd25fc2luZ2xlciIKICAgIHNpbmdsZXIgPSBpZmVsc2UoaXMubmEocHJ1bmVkLmxhYmVscyksIlVua25vd24iLCBwcnVuZWQubGFiZWxzKQogICkgfD4KICBkcGx5cjo6c2VsZWN0KGNlbGxfaWQsIHNpbmdsZXIsIHNpbmdsZXJfZGVsdGFfbmV4dCA9IGRlbHRhLm5leHQpCmBgYAoKUmVhZCBhbmQgcHJlcGFyZSBzY2FudmkgcmVzdWx0czoKCmBgYHtyfQpzY2FudmlfZnVsbF9kZiA8LSByZWFkcjo6cmVhZF90c3YoCiAgZmlsZS5wYXRoKHNjYW52aV9kaXIsICJzY2FudmlfcHJlZGljdGlvbnMudHN2IikKKSB8PgogIGRwbHlyOjpzZWxlY3QoCiAgICBjZWxsX2lkLCAKICAgIHNjYW52aSA9IHNjYW52aV9wcmVkaWN0aW9uLCAKICAgIHN0YXJ0c193aXRoKCJwcF8iKQogICkgfD4KICB0aWR5cjo6cGl2b3RfbG9uZ2VyKAogICAgc3RhcnRzX3dpdGgoInBwXyIpLCAKICAgIG5hbWVzX3RvID0gInBvc3Rlcmlvcl9jZWxsdHlwZSIsIAogICAgdmFsdWVzX3RvID0gInBvc3RlcmlvciIKICApIHw+CiAgZHBseXI6Om11dGF0ZShwb3N0ZXJpb3JfY2VsbHR5cGUgPSBzdHJpbmdyOjpzdHJfcmVtb3ZlKHBvc3Rlcmlvcl9jZWxsdHlwZSwgIl5wcF8iKSkKCiMgY3JlYXRlIGRlZGljYXRlZCBjb2x1bW4gZm9yIHRoZSBwb3N0ZXJpb3IgYXNzb2NpYXRlZCB3aXRoIHRoZSBsYWJlbApzY2FudmlfZGYgPC0gc2NhbnZpX2Z1bGxfZGYgfD4KICBkcGx5cjo6ZmlsdGVyKHNjYW52aSA9PSBwb3N0ZXJpb3JfY2VsbHR5cGUpIHw+CiAgZHBseXI6OnNlbGVjdChjZWxsX2lkLCBzY2FudmksIHNjYW52aV9wb3N0ZXJpb3IgPSBwb3N0ZXJpb3IpCmBgYAoKClJlYWQgYW5kIHByZXBhcmUgYWRkaXRpb25hbCBoZWxwZXIgZmlsZXMgZm9yIHZpejoKCmBgYHtyfQojIHZhbGlkYXRpb24gZ3JvdXBzIGRhdGEgZnJhbWUKdmFsaWRhdGlvbl9kZiA8LSByZWFkcjo6cmVhZF90c3YodmFsaWRhdGlvbl91cmwpIHw+CiAgZHBseXI6OnNlbGVjdChjb25zZW5zdXNfYW5ub3RhdGlvbiwgdmFsaWRhdGlvbl9ncm91cF9hbm5vdGF0aW9uKQoKIyBzZXQgdXAgcGFsZXR0ZXMKcmVjb2RlZF9wYWxldHRlX2RmIDwtIHJlYWRyOjpyZWFkX3RzdihyZWNvZGVkX3BhbGV0dGVfZmlsZSkKcmVjb2RlZF9jZWxsdHlwZV9wYWwgPC0gcmVjb2RlZF9wYWxldHRlX2RmJGhleF9jb2xvcgpuYW1lcyhyZWNvZGVkX2NlbGx0eXBlX3BhbCkgPC0gcmVjb2RlZF9wYWxldHRlX2RmJGhhcm1vbml6ZWRfbGFiZWwKIyBmb3IgdGhpcyBub3RlYm9vaywgd2Ugd2FudCBqdXN0IGEgc2luZ2xlIFVua25vd24KcmVjb2RlZF9jZWxsdHlwZV9wYWxbIlVua25vd24iXSA8LSByZWNvZGVkX2NlbGx0eXBlX3BhbFsidW5rbm93bl9zaW5nbGVyIl0KCgpuYmF0bGFzX3BhbGV0dGVfZGYgPC0gcmVhZHI6OnJlYWRfdHN2KG5iYXRsYXNfcGFsZXR0ZV9maWxlKQpuYmF0bGFzX2NlbGx0eXBlX3BhbCA8LSBuYmF0bGFzX3BhbGV0dGVfZGYkaGV4X2NvbG9yCm5hbWVzKG5iYXRsYXNfY2VsbHR5cGVfcGFsKSA8LSBuYmF0bGFzX3BhbGV0dGVfZGYkTkJBdGxhc19sYWJlbAoKIyBzYW1wbGUgbWV0YWRhdGEKc2FtcGxlX21ldGFkYXRhIDwtIHJlYWRyOjpyZWFkX3RzdihzYW1wbGVfbWV0YWRhdGFfZmlsZSkKCiMgbGFiZWwgbWFwcGluZwpuYmF0bGFzX2xhYmVsX21hcCA8LSByZWFkcjo6cmVhZF90c3YobmJhdGxhc19sYWJlbF9tYXBfZmlsZSkKYGBgCgoKIyMgc2NBTlZJIHBvc3RlcmlvciBwcm9iYWJpbGl0aWVzCgpCZWZvcmUgY29tcGFyaW5nIHRvIGBTaW5nbGVSYCwgd2Ugc2hvdWxkIGdldCBhIHNlbnNlIG9mIHJlbGlhYmlsaXR5IGZvciB0aGUgYHNjQU5WSWAgcmVzdWx0cy4KYHNjQU5WSWAgcmV0dXJucyBhIGNlbGwtc3BlY2lmaWMgcG9zdGVyaW9yIHByb2JhYmlsaXR5IGZvciBlYWNoIGxhYmVsLCBzbyB3ZSdsbCBiZWdpbiBieSBsb29raW5nIGF0IHRoZXNlIGRpc3RyaWJ1dGlvbnMuCgpGaXJzdCwgd2hhdCBpcyB0aGUgZGlzdHJpYnV0aW9uIG9mIHBvc3RlcmlvciBwcm9iYWJpbGl0aWVzIGZvciBhc3NpZ25lZCBsYWJlbHM/CldlJ2xsIG1ha2UgYSBoaXN0b2dyYW0gYmVsb3cgdXNpbmcgYSB2ZXJ5IHZpc2libGUgY29sb3IgdG8gZW5zdXJlIHRoZSBwbG90IGlzIGxlZ2libGUsIHdoZXJlIGVhY2ggcGFuZWwgaXMgYSBsaWJyYXJ5LCBhbmQgbGlicmFyaWVzIGFyZSBvcmRlcmVkIGJ5IHNpemUuCgpgYGB7ciBmaWcuaGVpZ2h0PTEyLCBmaWcud2lkdGg9MTV9CnNjYW52aV9kZiB8PgogIHRpZHlyOjpzZXBhcmF0ZShjZWxsX2lkLCBpbnRvID0gYygibGlicmFyeV9pZCIsICJiYXJjb2RlIikpIHw+CiAgZHBseXI6OmFkZF9jb3VudChsaWJyYXJ5X2lkKSB8PgogIGRwbHlyOjptdXRhdGUoCiAgICBsaWJyYXJ5X2lkID0gZ2x1ZTo6Z2x1ZSgie2xpYnJhcnlfaWR9IChOPXtufSkiKSwKICAgIGxpYnJhcnlfaWQgPSBmYWN0b3IobGlicmFyeV9pZCksIAogICAgbGlicmFyeV9pZCA9IGZvcmNhdHM6OmZjdF9pbmZyZXEobGlicmFyeV9pZCksIAogICAgbGlicmFyeV9pZCA9IGZvcmNhdHM6OmZjdF9yZXYobGlicmFyeV9pZCkKICApIHw+CiAgIyMjIyMjIyMjIyMjIGludG8gZ2dwbG90ICMjIyMjIyMjIyMjIyMjIyMKICBnZ3Bsb3QoKSArIAogIGFlcyh4ID0gc2NhbnZpX3Bvc3RlcmlvcikgKyAKICAjIHVnbHkgYnV0IF92ZXJ5IHZpc2libGVfIGNvbG9yCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDEwMCwgZmlsbCA9ICJtYWdlbnRhIiwgY29sb3IgPSAibWFnZW50YSIpICsgCiAgZmFjZXRfd3JhcCh2YXJzKGxpYnJhcnlfaWQpLCBzY2FsZXMgPSAiZnJlZV95IiwgbmNvbCA9IDcpCmBgYAoKCkFjcm9zcyBsaWJyYXJpZXMsIHRoZSBtZWRpYW4gcG9zdGVyaW9yIHByb2JhYmlsaXR5IGlzIGFsd2F5cyBleGNlcHRpb25hbGx5IGhpZ2ggd2hpY2ggc3VnZ2VzdHMgYHNjQU5WSWAgY29uZmlkZW5jZSBpcyByb3VnaGx5IHRoZSBzYW1lIGFjcm9zcyBsaWJyYXJpZXMuCgpMZXQncyBpbnN0ZWFkIGxvb2sgYXQgdGhlc2UgcHJvYmFiaWxpdGllcyBvbiBhIHBlci1jZWxsIHR5cGUgYmFzaXMsIHRoaXMgdGltZSB3aXRoIGEgcmlkZ2UgcGxvdCBpbmNsdWRpbmcgYSBsaW5lIGZvciB0aGUgbWVkaWFuIHZhbHVlIGZvciBlYWNoIGRpc3RyaWJ1dGlvbjoKCmBgYHtyIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTh9CnNjYW52aV9yaWRnZXBsb3QgPC0gc2NhbnZpX2RmIHw+CiAgZHBseXI6Omdyb3VwX2J5KHNjYW52aSkgfD4KICBkcGx5cjo6bXV0YXRlKHNjYW52aSA9IGdsdWU6OmdsdWUoIntzY2Fudml9IChuPXtkcGx5cjo6bigpfSkiKSkgfD4KICAjIyMjIyMjIyMjIyBpbnRvIGdncGxvdCAjIyMjIyMjIyMjIyMKICBnZ3Bsb3QoKSArIAogICAgYWVzKHkgPSBmb3JjYXRzOjpmY3RfaW5mcmVxKHNjYW52aSksIHggPSBzY2FudmlfcG9zdGVyaW9yKSArIAogICAgIyBzaG93IHRoZSBtZWRpYW4KICAgIGdncmlkZ2VzOjpzdGF0X2RlbnNpdHlfcmlkZ2VzKHF1YW50aWxlX2xpbmVzID0gVFJVRSwgcXVhbnRpbGVzID0gMiwgYWxwaGEgPSAwLjcpICsKICAgIGxhYnMoeCA9ICJwb3N0ZXJpb3IgcHJvYmFiaWxpdHkiLCB5ID0gInNjQU5WSSBsYWJlbCAobnVtYmVyIG9mIGNlbGxzKSIpIApzY2FudmlfcmlkZ2VwbG90CmBgYAoKV2hpbGUgYW5ub3RhdGlvbiByZWxpYWJpbGl0eSBpcyBhYm91dCB0aGUgc2FtZSBvbiBhIHBlci1saWJyYXJ5IGJhc2lzLCBpdCBzZWVtcyB0aGF0IGNlcnRhaW4gY2VsbCB0eXBlcyBtYXkgYmUgbXVjaCBlYXNpZXIgdG8gY2xhc3NpZnkgdGhhbiBvdGhlcnMgd2hpY2ggbWF5IGJlIGEgZmFjdG9yIG9mIGhvdyB3ZWxsIHJlcHJlc2VudGVkIHRoZXkgYXJlIGluIHRoZSBgTkJBdGxhc2AgcmVmZXJlbmNlLgpUaGVyZSBhcmUgdmVyeSBmZXcgY2VsbHMgaW4gZ2VuZXJhbCBhc3NvY2lhdGVkIHdpdGggdGhlIGNlbGwgdHlwZXMgdGhhdCBoYXZlIHZlcnkgbG93IG1lZGlhbiBwb3N0ZXJpb3IgcHJvYmFiaWxpdGllcyAoYE5LVCBjZWxsYCwgYE1pZ3JhdG9yeSBjRENgLCBgQ2lyY3VsYXRpbmcgTksgY2VsbGApLgoKQSBgMC43NWAgdGhyZXNob2xkIHNlZW1zIGxpa2UgaXQgbWF5IGJlIGEgZ29vZCBtaWRkbGUtZ3JvdW5kIGZvciBwcmVzZXJ2aW5nIGxhYmVscyB3aGlsZSByZW1vdmluZyBsb3ctY29uZmlkZW5jZSBhbm5vdGF0aW9ucy4KV2hhdCBmcmFjdGlvbiBvZiBjZWxscyB3aWxsIHRoaXMgcmVtb3ZlPwoKYGBge3J9CnN1bShzY2FudmlfZGYkc2NhbnZpX3Bvc3RlcmlvciA8IDAuNzUpIC8gbnJvdyhzY2FudmlfZGYpCmBgYAoKV2hhdCBhcmUgdGhvc2UgY2VsbCB0eXBlcz8KCmBgYHtyfQpzY2FudmlfZGYgfD4KICBkcGx5cjo6ZmlsdGVyKHNjYW52aV9wb3N0ZXJpb3IgPCAwLjc1KSB8PgogIGRwbHlyOjpjb3VudChzY2FudmkpIHw+CiAgZHBseXI6OmFycmFuZ2UoZGVzYyhuKSkKYGBgCk5vdGFibHksIG5vbmUgYXJlIE5ldXJvZW5kb2NyaW5lICh0aGUgdHVtb3IgdHlwZSkuCgpXZSdsbCBhcHBseSB0aGUgMC43NSB0aHJlc2hvbGQgYmVsb3cgYnkgcmVjb2RpbmcgdGhlc2UgY2VsbHMgdG8gYFVua25vd25gLgoKYGBge3J9CnBwX3RocmVzaG9sZCA8LSAwLjc1CnNjYW52aV9kZiA8LSBzY2FudmlfZGYgfD4KICBkcGx5cjo6bXV0YXRlKHNjYW52aSA9IGlmZWxzZSgKICAgIHNjYW52aV9wb3N0ZXJpb3IgPCBwcF90aHJlc2hvbGQsIAogICAgIlVua25vd24iLAogICAgc2NhbnZpCiAgKSkKYGBgCgojIyBDb21wYXJlIGxhYmVscwoKV2UnbGwgY3JlYXRlIGEgc2luZ2xlIGRhdGEgZnJhbWUgd2l0aCBVTUFQIGNvb3JkaW5hdGVzIGFuZCBhbGwgY2VsbCB0eXBlcyAoY29uc2Vuc3VzLCBgU2luZ2xlUmAsIGFuZCBgc2NBTlZJYCkuIApXZSdsbCBkbyB0aGlzIGF0IHR3byBsZXZlbHMgb2Ygb3JnYW5pemF0aW9uOgoKLSBPbmUgd2l0aCBoYXJtb25pemVkIGxhYmVscyB0byBiZSBhYmxlIHRvIGNvbXBhcmUgdG8gY29uc2Vuc3VzCi0gT25lIHdpdGggb3JpZ2luYWwgTkJBdGxhcyBsYWJlbHMgdG8ganVzdCBjb21wYXJlIGBTaW5nbGVSYCB0byBgc2NBTlZJYAoKCmBgYHtyfQpjZWxsdHlwZV9kZiA8LSBzY3V0dGxlOjptYWtlUGVyQ2VsbERGKAogIG1lcmdlZF9zY2UsCiAgdXNlLmNvbGRhdGEgPSBjKCJzYW1wbGVfaWQiLCAiaXNfeGVub2dyYWZ0IiwgImNvbnNlbnN1c19jZWxsdHlwZV9hbm5vdGF0aW9uIiksCiAgdXNlLmRpbXJlZCA9IGMoIlVNQVAiKQopIHw+CiAgIyB0aGVyZSBBUkUgcmVwZWF0ZWQgYmFyY29kZXMgc28gd2UgbmVlZCB0byBrZWVwIGNlbGxfaWQgYXJvdW5kCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oImNlbGxfaWQiKSB8PgogIGRwbHlyOjpyZW5hbWUoCiAgICBVTUFQMSA9IFVNQVAuMSwKICAgIFVNQVAyID0gVU1BUC4yLAogICAgY29uc2Vuc3VzX2Fubm90YXRpb24gPSBjb25zZW5zdXNfY2VsbHR5cGVfYW5ub3RhdGlvbgogICkgfD4KICBkcGx5cjo6bGVmdF9qb2luKHZhbGlkYXRpb25fZGYsIGJ5ID0gImNvbnNlbnN1c19hbm5vdGF0aW9uIikgfD4KICAjIHJlY29kZSBjb25zZW5zdXMgTkFzIHRvICJVbmtub3duIgogIGRwbHlyOjptdXRhdGUoCiAgICB2YWxpZGF0aW9uX2dyb3VwX2Fubm90YXRpb24gPSBpZmVsc2UoCiAgICAgIGlzLm5hKHZhbGlkYXRpb25fZ3JvdXBfYW5ub3RhdGlvbiksICJVbmtub3duIiwgdmFsaWRhdGlvbl9ncm91cF9hbm5vdGF0aW9uCiAgICApCiAgKSB8PgogIGRwbHlyOjpzZWxlY3QoLWNvbnNlbnN1c19hbm5vdGF0aW9uKSB8PgogICMgbWVyZ2UgaW4gdGhlIGFubm90YXRpb25zCiAgZHBseXI6OmxlZnRfam9pbihzaW5nbGVyX2RmLCBieSA9ICJjZWxsX2lkIikgfD4KICBkcGx5cjo6bGVmdF9qb2luKHNjYW52aV9kZiwgYnkgPSAiY2VsbF9pZCIpIHw+CiAgIyBtYWtlIGl0IHRpZHkKICB0aWR5cjo6cGl2b3RfbG9uZ2VyKAogICAgYyh2YWxpZGF0aW9uX2dyb3VwX2Fubm90YXRpb24sIHNpbmdsZXIsIHNjYW52aSksIAogICAgbmFtZXNfdG8gPSAiY2VsbHR5cGVfbWV0aG9kIiwgCiAgICB2YWx1ZXNfdG8gPSAibGFiZWwiCiAgKSB8PgogIGhhcm1vbml6ZV9jZWxsdHlwZXMobGFiZWwsIGxhYmVsX3JlY29kZWQpIHw+CiAgZHBseXI6Om11dGF0ZShsYWJlbCA9IGlmZWxzZShsYWJlbCA9PSAiTkUiLCAiTmV1cm9lbmRvY3JpbmUiLCBsYWJlbCkpIHw+CiAgdGlkeXI6OnNlcGFyYXRlKGNlbGxfaWQsIGludG8gPSBjKCJsaWJyYXJ5X2lkIiwgImJhcmNvZGUiKSwgcmVtb3ZlID0gRkFMU0UpCmBgYAoKCiMjIFVNQVAKClRoaXMgc2VjdGlvbiBkaXNwbGF5cyB0aGUgVU1BUCBmb3IgdGhlIF9tZXJnZWQgb2JqZWN0XyAobm90IGludGVncmF0ZWQpIHdpdGggY2VsbCB0eXBlIGxhYmVscyBhY3Jvc3MgbWV0aG9kcy4KV2Ugc2hvdyBhIHBhbmVsIGZvciBlYWNoIGNlbGwgdHlwZSBtZXRob2QuCgojIyMgU2luZ2xlUiB2cyBzY0FOVkkKClRoaXMgc2V0IG9mIFVNQVBzIHNob3dzIGFubm90YXRpb24gcmVzdWx0cyBmcm9tIGBTaW5nbGVSYCBhbmQgYHNjQU5WSWAuCgpTaW5jZSB0aGVyZSBhcmUgbWFueSBjZWxsIHR5cGVzIGluIHRoaXMgcGxvdCwgaXQgaGFzIGJlZW4gY29sb3JlZCBzbyB0aGF0IGFsbCBjZWxsIHR5cGVzIGluIGEgZ2l2ZW4gImZhbWlseSIgZWFjaCBzaGFyZSBhIGNvbG9yLCB3aGVyZSB3ZSBjb25zaWRlciB0aGVzZSBmYW1pbHkgZ3JvdXBpbmdzOgoKKiBUIGNlbGxzCiogTmF0dXJhbCBraWxsZXIgY2VsbHMKKiBDb252ZW50aW9uYWwgZGVuZHJpdGljIGNlbGxzCiogTW9ub2N5dGVzCgpgYGB7ciwgZmlnLndpZHRoID0gMTIsIGZpZy5oZWlnaHQgPSA4fQpsYWJlbF9vcmRlciA8LSBuYmF0bGFzX2xhYmVsX21hcCB8PgogIGRwbHlyOjphZGRfY291bnQoTkJBdGxhc19mYW1pbHkpIHw+CiAgZHBseXI6OmFycmFuZ2UoZGVzYyhuKSwgTkJBdGxhc19mYW1pbHkpIHw+CiAgZHBseXI6OnB1bGwoTkJBdGxhc19sYWJlbCkKbGFiZWxfb3JkZXIgPC0gYyhsYWJlbF9vcmRlciwgIlVua25vd24iKQoKdW1hcF9kaXJlY3RfZGYgPC0gY2VsbHR5cGVfZGYgfD4KICBkcGx5cjo6ZmlsdGVyKGNlbGx0eXBlX21ldGhvZCAhPSAidmFsaWRhdGlvbl9ncm91cF9hbm5vdGF0aW9uIikgfD4KICAjIHNodWZmbGUgcG9pbnRzIHRvIGVuc3VyZSBvcmRlciB0aGV5IGFyZSBwbG90dGVkIGlzIHJhbmRvbQogIGRwbHlyOjpzYW1wbGVfZnJhYygxKSB8PiAKICAjIG9yZGVyIGNlbGx0eXBlcyBzbyB0aGF0IGZhbWlsaWVzIGFyZSBncm91cGVkIHRvZ2V0aGVyIGZvciBhIGNsZWFuZXIgbGVnZW5kCiAgIyBub3RlIHRoaXMgd2lsbCBnaXZlIHdhcm5pbmdzIGlmIGFueSBvZiB0aG9zZSBjZWxscyBhcmVuJ3QgaW4gdGhlIGFubm90YXRpb24sIGJ1dCB0aGF0J3MgZmluZQogIGRwbHlyOjptdXRhdGUobGFiZWwgPSBmYWN0b3IobGFiZWwsIGxldmVscyA9IGxhYmVsX29yZGVyKSkKICAKZ2dwbG90KHVtYXBfZGlyZWN0X2RmKSArCiAgYWVzKHggPSBVTUFQMSwgeSA9IFVNQVAyLCBjb2xvciA9IGxhYmVsKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSwgc2l6ZSA9IDAuMikgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBuYmF0bGFzX2NlbGx0eXBlX3BhbCkgKwogIGZhY2V0X3dyYXAodmFycyhjZWxsdHlwZV9tZXRob2QpKSArCiAgdW1hcF90aGVtZSArCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDIsIGFscGhhID0gMSkpKQpgYGAKCkNlbGwgdHlwZSBhbm5vdGF0aW9ucyBsb29rIGJyb2FkbHkgY29uc2lzdGVudCBiZXR3ZWVuIHRoZXNlIG1ldGhvZHMsIGFsdGhvdWdoIGBTaW5nbGVSYCBhcHBlYXJzIHRvIGFzc2lnbiBtb3JlIGhldGVyb2dlbmVpdHkgd2l0aGluIHRoZSAiY2x1bXBzIiBvZiBjZWxscyAod2hpY2ggbGlrZWx5IGNvcnJlc3BvbmQgdG8gaW5kaXZpZHVhbCBsaWJyYXJpZXMgaW4gdGhpcyBtZXJnZWQgb2JqZWN0KS4KCgojIyMgSGFybW9uaXplZCBsYWJlbHMgZm9yIGNvbnNlbnN1cyBjb21wYXJpc29uCgpUaGlzIHNldCBvZiBVTUFQcyBoYXJtb25pemVzIGNlbGwgdHlwZSBsYWJlbHMgaW50byBicm9hZGVyIGdyb3VwaW5ncyB0byBlbmFibGUgYSBjb21wYXJpc29uIHdpdGggY29uc2Vuc3VzIGFubm90YXRpb25zLgpOb3RlIHRoYXQgdGhlIGBVbmtub3duYCBjZWxsIHR5cGUgcmVmZXJzIHRvIGNlbGxzIHVuY2F0ZWdvcml6ZWQgYnkgYFNpbmdsZVJgIG9yIGBzY0FOVklgLgoKYGBge3IsIGZpZy53aWR0aCA9IDEyLCBmaWcuaGVpZ2h0ID0gOH0KZ2dwbG90KGNlbGx0eXBlX2RmKSArCiAgYWVzKHggPSBVTUFQMSwgeSA9IFVNQVAyLCBjb2xvciA9IGxhYmVsX3JlY29kZWQpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41LCBzaXplID0gMC4yKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHJlY29kZWRfY2VsbHR5cGVfcGFsKSArCiAgZmFjZXRfd3JhcCh2YXJzKGNlbGx0eXBlX21ldGhvZCkpICsKICB1bWFwX3RoZW1lICsKICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gMiwgYWxwaGEgPSAxKSkpCmBgYApXZSBzZWUgYnJvYWQgY29tcGF0aWJpbGl0eSBhbW9uZyBhbGwgdGhyZWUgYXBwcm9hY2hlcywgYW5kIHRoZSBVbmtub3duIGNvbnNlbnN1cyBpbmRlZWQgYXBwZWFyIHRvIGJlIG1vc3RseSB0dW1vciBjZWxscyAoaGVyZSwgTmV1cm9lbmRvY3JpbmUpLgpUaGlzIGlzIHJlYXNzdXJpbmcgdGhhdCB3ZSBoYXZlIHNvbWUgcm9idXN0IGNlbGwgdHlwZSBpZGVudGlmaWNhdGlvbnMgaGVyZSEKCgojIyBIZWF0bWFwCgpUaGlzIHNlY3Rpb24gc2hvd3MgaGVhdG1hcHMgY29tcGFyaW5nIGFubm90YXRpb25zIHRvIG9uZSBhbm90aGVyLgoKCiMjIyBTaW5nbGVSIHZzIHNjQU5WSQoKVGhpcyBoZWF0bWFwIGRpcmVjdGx5IGNvbXBhcmVzIGBTaW5nbGVSYCBhbmQgYHNjQU5WSWAgYW5ub3RhdGlvbnMuCgpgYGB7ciwgZmlnLmhlaWdodCA9IDksIGZpZy53aWR0aCA9IDl9IAojIG1ha2UgYSB3aWRlIHZlcnNpb24gZm9yIHRoZSBoZWF0bWFwCmNlbGx0eXBlX3dpZGVfZGYgPC0gY2VsbHR5cGVfZGYgfD4KICBkcGx5cjo6ZmlsdGVyKGNlbGx0eXBlX21ldGhvZCAhPSAidmFsaWRhdGlvbl9ncm91cF9hbm5vdGF0aW9uIikgfD4KICBkcGx5cjo6c2VsZWN0KC1sYWJlbF9yZWNvZGVkKSB8PgogIHRpZHlyOjpwaXZvdF93aWRlcigKICAgIG5hbWVzX2Zyb20gPSBjZWxsdHlwZV9tZXRob2QsCiAgICB2YWx1ZXNfZnJvbSA9IGxhYmVsCiAgKQoKbWFrZV9qYWNjYXJkX2hlYXRtYXAoCiAgY2VsbHR5cGVfd2lkZV9kZiwKICAic2NhbnZpIiwKICAic2luZ2xlciIsCiAgInNjQU5WSSBsYWJlbCIsCiAgIlNpbmdsZVIgbGFiZWwiCikKYGBgCgpUaGlzIGhlYXRtYXAgc2hvd3MgZXhjZWxsZW50IGNvcnJlc3BvbmRlbmNlIGJldHdlZW4gYFNpbmdsZVJgIGFuZCBgc2NBTlZJYCBhbm5vdGF0aW9ucyB3aXRoIG1vc3Qgb2YgdGhlIHdlaWdodCBhbG9uZyB0aGUgZGlhZ29uYWwuCgpXZSdsbCBuYWlsIHRoaXMgZG93biBhIGJpdCBmdXJ0aGVyIHdpdGggc29tZSBjb25mdXNpb24gbWF0cml4IG1ldHJpY3M6CgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KIyBlbnN1cmUgZmFjdG9ycyBhcmUgY29tcGF0aWJsZSBmb3IgY2FyZXQKYWxsX2xldmVscyA8LSBjKAogIGNlbGx0eXBlX3dpZGVfZGYkc2NhbnZpLCAKICBjZWxsdHlwZV93aWRlX2RmJHNpbmdsZXIKKSB8PiB1bmlxdWUoKQoKY2FyZXQ6OmNvbmZ1c2lvbk1hdHJpeCgKICBmYWN0b3IoY2VsbHR5cGVfd2lkZV9kZiRzY2FudmksIGxldmVscyA9IGFsbF9sZXZlbHMpLCAgCiAgZmFjdG9yKGNlbGx0eXBlX3dpZGVfZGYkc2luZ2xlciwgbGV2ZWxzID0gYWxsX2xldmVscykKKSRvdmVyYWxsCgpgYGAKCiMjIyBIYXJtb25pemVkIGxhYmVscyBmb3IgY29uc2Vuc3VzIGNvbXBhcmlzb24KClRoaXMgc3RhY2tlZCBoZWF0bWFwIGNvbXBhcmVzIGFsbCB0aHJlZSBtZXRob2RzIHRvIG9uZSBhbm90aGVyLCB1c2luZyBicm9hZGVyIGNlbGwgdHlwZSBncm91cGluZ3MgdG8gZW5hYmxlIGEgbW9yZSBkaXJlY3QgY29tcGFyaXNvbiB3aXRoIGNvbnNlbnN1cyBhbm5vdGF0aW9ucy4KCmBgYHtyLCBmaWcuaGVpZ2h0ID0gMTMsIGZpZy53aWR0aCA9IDEyfSAKIyBtYWtlIGEgd2lkZSB2ZXJzaW9uIGZvciB0aGUgaGVhdG1hcApjZWxsdHlwZV9yZWNvZGVkX3dpZGVfZGYgPC0gY2VsbHR5cGVfZGYgfD4KICBkcGx5cjo6c2VsZWN0KC1sYWJlbCkgfD4KICB0aWR5cjo6cGl2b3Rfd2lkZXIoCiAgICBuYW1lc19mcm9tID0gY2VsbHR5cGVfbWV0aG9kLAogICAgdmFsdWVzX2Zyb20gPSBsYWJlbF9yZWNvZGVkCiAgKQoKbGlzdCgKICAiU2luZ2xlUiBhbm5vdGF0aW9ucyIgPSBtYWtlX2phY2NhcmRfbWF0cml4KGNlbGx0eXBlX3JlY29kZWRfd2lkZV9kZiwgInZhbGlkYXRpb25fZ3JvdXBfYW5ub3RhdGlvbiIsICJzaW5nbGVyIiksCiAgInNjQU5WSSBhbm5vdGF0aW9ucyIgPSBtYWtlX2phY2NhcmRfbWF0cml4KGNlbGx0eXBlX3JlY29kZWRfd2lkZV9kZiwgInZhbGlkYXRpb25fZ3JvdXBfYW5ub3RhdGlvbiIsICJzY2FudmkiKQopIHw+CiAgcHVycnI6OmltYXAoCiAgICBcKGphY2NhcmRfbWF0LCBuYW1lKSBjcmVhdGVfc2luZ2xlX2hlYXRtYXAoCiAgICAgICAgbWF0ID0gamFjY2FyZF9tYXQsCiAgICAgICAgcm93X3RpdGxlID0gbmFtZSwKICAgICAgICBjb2x1bW5fdGl0bGUgPSAiQ29uc2Vuc3VzIHZhbGlkYXRpb24gZ3JvdXBzIiwKICAgICAgICBrZWVwX2xlZ2VuZF9uYW1lID0gIlNpbmdsZVIgYW5ub3RhdGlvbnMiICMgYXJiaXRyYXJ5IGZvciBsYWJlbGluZwogICAgKQogICkgfD4KICAjIGNvbmNhdGVuYXRlIHZlcnRpY2FsbHkgaW50byBIZWF0bWFwTGlzdCBvYmplY3QKICBwdXJycjo6cmVkdWNlKENvbXBsZXhIZWF0bWFwOjpgJXYlYCkgfD4KICBDb21wbGV4SGVhdG1hcDo6ZHJhdyhoZWF0bWFwX2xlZ2VuZF9zaWRlID0gInJpZ2h0IikKYGBgCgpBZ2Fpbiwgd2Ugc2VlIGV4Y2VsbGVudCBjb3JyZXNwb25kZW5jZSBhbW9uZyBhbGwgdGhyZWUgbWV0aG9kcy4KCgoKIyMgQ3Jvc3MtbGlicmFyeSBjb21wYXJpc29uCgpBY3Jvc3MgbGlicmFyaWVzLCB3aGF0IGZyYWN0aW9uIG9mIGBTaW5nbGVSYCBhbmQgYHNjQU5WSWAgbGFiZWxzIGV4YWN0bHkgYWdyZWU/CkZvciB0aGlzLCB3ZSdsbCBjb21wYXJlIGFtb25nOgoKKiBGcmFjdGlvbiBvZiBpZGVudGljYWwgbGFiZWxzCiogRnJhY3Rpb24gb2YgbGFiZWxzIHRoYXQgYWdyZWUgYXQgbGVhc3Qgb24gdGhlIGxldmVsIG9mIHRoZWlyIGNlbGwgdHlwZSAiZmFtaWx5LCIgd2hlcmUgd2UgZGVmaW5lIHRoZXNlIGZhbWlsaWVzIGJhc2VkIG9uIGdyb3VwcyBvZiBOQkF0bGFzIGFubm90YXRpb25zOgogICogVCBjZWxsczogYFQgY2VsbGAsIGBUcmVnYCwgYENENCsgVCBjZWxsYCwgYENEOCsgVCBjZWxsYAogICogTmF0dXJhbCBraWxsZXIgY2VsbHM6IGBOSyBjZWxsYCwgYENpcmN1bGF0aW5nIE5LIGNlbGxgLCBgUmVzaWRlbnQgTksgY2VsbGAsIGBUT1gyKy9LSVQrIE5LIGNlbGxgCiAgKiBDb252ZW50aW9uYWwgZGVuZHJpdGljIGNlbGxzIChjREMpOiBgY0RDYCwgYGNEQzFgLCBgY0RDMi9EQzNgLCBgTWlncmF0b3J5IGNEQ2AKICAqIE1vbm9jeXRlczogYE1vbm9jeXRlYCwgYENsYXNzaWNhbCBtb25vY3l0ZWAsIGBQYXRyb2xsaW5nIG1vbm9jeXRlYAoqIEZyYWN0aW9uIG9mIGxhYmVscyB0aGF0IGVudGlyZWx5IGRpc2FncmVlCgpUaGUgdGFibGUgYmVsb3cgc2hvd3MgdGhlc2UgZnJhY3Rpb25zIGZvciBlYWNoIGxpYnJhcnkgYWxvbmcgd2l0aCB0aGUgbGlicmFyeSBzaXplLgpXZSBhbHNvIHZpc3VhbGl6ZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gbGlicmFyeSBzaXplIGFuZCBmYW1pbHktYmFzZWQgYWdyZWVtZW50LgoKYGBge3J9CiMgY2VsbHR5cGVfd2lkZV9kZiB1c2VzIHRoZSBvcmlnaW5hbCBsYWJlbHMKaWRlbnRpY2FsX2FncmVlX2RmIDwtIGNlbGx0eXBlX3dpZGVfZGYgfD4KICBkcGx5cjo6Z3JvdXBfYnkobGlicmFyeV9pZCkgfD4KICBkcGx5cjo6c3VtbWFyaXplKAogICAgZnJhY19pZGVudGljYWwgPSBzdW0oc2NhbnZpID09IHNpbmdsZXIpL2RwbHlyOjpuKCksIAogICAgbGlicmFyeV9zaXplID0gZHBseXI6Om4oKQogICkKCiMgY3JlYXRlIGEgd2lkZSBkYXRhIGZyYW1lIHdpdGggImZhbWlseSIgbGFiZWxzIHdoZXJlIHBvc3NpYmxlCmNlbGx0eXBlX2ZhbWlseV9kZiA8LSBjZWxsdHlwZV9kZiB8PgogIGRwbHlyOjpmaWx0ZXIoY2VsbHR5cGVfbWV0aG9kICE9ICJ2YWxpZGF0aW9uX2dyb3VwX2Fubm90YXRpb24iKSB8PgogIGRwbHlyOjpsZWZ0X2pvaW4oCiAgICBuYmF0bGFzX2xhYmVsX21hcCwgYnkgPSBjKCJsYWJlbCIgPSAiTkJBdGxhc19sYWJlbCIpCiAgKSB8PgogIGRwbHlyOjpzZWxlY3QoY2VsbF9pZCwgbGlicmFyeV9pZCwgY2VsbHR5cGVfbWV0aG9kLCBsYWJlbCkgfD4KICB0aWR5cjo6cGl2b3Rfd2lkZXIoCiAgICBuYW1lc19mcm9tID0gY2VsbHR5cGVfbWV0aG9kLAogICAgdmFsdWVzX2Zyb20gPSBsYWJlbAogICkKCmZhbWlseV9hZ3JlZV9kZiA8LSBjZWxsdHlwZV9mYW1pbHlfZGYgfD4KICBkcGx5cjo6Z3JvdXBfYnkobGlicmFyeV9pZCkgfD4KICBkcGx5cjo6c3VtbWFyaXplKGZyYWNfc2FtZV9mYW1pbHkgPSBzdW0oc2NhbnZpID09IHNpbmdsZXIpL2RwbHlyOjpuKCkpCgojIGlmIGZhbWlsaWVzIGRpc2FncmVlLCB0aGVuIHRoZXJlIGlzIG5vIGFncmVlbWVudCBhdCBhbGwKZGlzYWdyZWVfZGYgPC0gY2VsbHR5cGVfZmFtaWx5X2RmIHw+CiAgZHBseXI6Omdyb3VwX2J5KGxpYnJhcnlfaWQpIHw+CiAgZHBseXI6OnN1bW1hcml6ZShmcmFjX2Rpc2FncmVlID0gc3VtKHNjYW52aSAhPSBzaW5nbGVyKS9kcGx5cjo6bigpKQpgYGAKCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQojIGNvbWJpbmUgaW50byBhIHNpbmdsZSB0YWJsZSBmb3IgZGlzcGxheSAmIHZpegpmcmFjX2RmIDwtIGlkZW50aWNhbF9hZ3JlZV9kZiB8PgogIGRwbHlyOjpsZWZ0X2pvaW4oZmFtaWx5X2FncmVlX2RmKSB8PgogIGRwbHlyOjpsZWZ0X2pvaW4oZGlzYWdyZWVfZGYpIHw+CiAgIyByb3VuZCB0byA0IGRlY2ltYWxzCiAgZHBseXI6Om11dGF0ZShkcGx5cjo6YWNyb3NzKGNvbnRhaW5zKCJmcmFjXyIpLCBcKHgpIHJvdW5kKHgsIDQpKSkgfD4KICBkcGx5cjo6c2VsZWN0KAogICAgbGlicmFyeV9pZCwgCiAgICBsaWJyYXJ5X3NpemUsIAogICAgZnJhY19pZGVudGljYWwsIAogICAgZnJhY19zYW1lX2ZhbWlseSwgCiAgICBmcmFjX2Rpc2FncmVlCiAgKSB8PgogIGRwbHlyOjphcnJhbmdlKGxpYnJhcnlfc2l6ZSkKCiMgdXNlIERUIGZvciBpbXByb3ZlZCBleHBsb3JhdGlvbiBhYmlsaXR5CkRUOjpkYXRhdGFibGUoZnJhY19kZiwgcm93bmFtZXMgPSBGQUxTRSkKYGBgCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCBmaWcud2lkdGggPSA1LCBmaWcuaGVpZ2h0ID0gM30KIyBhZGQgUERYIGNvbHVtbiBmb3IgcGxvdHRpbmcKZnJhY19kZiA8LSBjZWxsdHlwZV9kZiB8PiAKICBkcGx5cjo6c2VsZWN0KGxpYnJhcnlfaWQsIGlzX3hlbm9ncmFmdCkgfD4KICB1bmlxdWUoKSB8PgogIGRwbHlyOjpyaWdodF9qb2luKGZyYWNfZGYpCgpnZ3Bsb3QoZnJhY19kZikgKyAKICBhZXMoeCA9IGxpYnJhcnlfc2l6ZSwgeSA9IGZyYWNfc2FtZV9mYW1pbHkpICsgCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBpc194ZW5vZ3JhZnQpKSArIAogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpICsKICBsYWJzKAogICAgeCA9ICJMaWJyYXJ5IHNpemUiLAogICAgeSA9ICJGcmFjdGlvbiBvZiBTaW5nbGVSL3NjQU5WSSBsYWJlbHNcbmZyb20gdGhlIHNhbWUgZmFtaWx5IgogICkgKwogICMgc3RvcCBnb2luZyBhYm92ZSAxCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMSkpCmBgYAoKTW9zdCBjZWxscyBoYXZlIGFncmVlaW5nIGFubm90YXRpb25zIGluIHRoZSBtYWpvcml0eSBvZiBsaWJyYXJpZXMuCkNvbnNpZGVyaW5nIGNsb3NlbHktcmVsYXRlZCBjZWxscyBpbiB0aGUgc2FtZSBmYW1pbHkgdXN1YWxseSBwcm92aWRlcyBhIGNvdXBsZSBtb3JlIGNlbGxzIHRvIGJlIGFubm90YXRlZCwgYnV0IG5vdCBhIGxhcmdlIHBlcmNlbnRhZ2UuClRoZXJlIGFwcGVhcnMgdG8gYmUgYSBzbGlnaHQgdHJlbmQgKG5vdCBzaWduaWZpY2FudCkgdGhhdCBsYXJnZXIgbGlicmFyaWVzIGhhdmUgbW9yZSBhZ3JlZW1lbnQgYmV0d2VlbiBtZXRob2RzLCBidXQgdGhpcyBhbHNvIHNlZW1zIGRyaXZlbiBieSB0aGUgbGlicmFyaWVzIHdpdGggbG93IGNvcnJlc3BvbmRlbmNlLgoKIyMjIExpYnJhcmllcyB3aXRoIGhpZ2ggbGV2ZWxzIG9mIGRpc2FncmVlbWVudCAKClRoZXJlIGFyZSBmb3VyIGxpYnJhcmllcyAoMiBQRFggYW5kIDIgdGlzc3VlKSB3aGljaCBzaG93IGxlc3MgdGhhbiA1MCUgYWdyZWVtZW50LCBzbyB3ZSBzaG91bGQgbG9vayBhdCB0aGVzZSBhIGJpdCBtb3JlIGNsb3NlbHkuCk5vdGFibHksIHRoZXNlIGRvIF9ub3RfIGNvcnJlc3BvbmQgdG8gdGhlIGZvdXIgc21hbGxlc3QgbGlicmFyaWVzLgoKSGVyZSBpcyB0aGUgc2FtcGxlIG1ldGFkYXRhIGZvciB0aGVzZSBmb3VyIHNhbXBsZXM6CgpgYGB7cn0KZGlzYWdyZWVfaWRzIDwtIGZyYWNfZGYgfD4KICBkcGx5cjo6ZmlsdGVyKGZyYWNfZGlzYWdyZWUgPj0gMC41KSB8PgogIGRwbHlyOjpwdWxsKGxpYnJhcnlfaWQpCgpkaXNhZ3JlZV9tZXRhZGF0YSA8LSBzYW1wbGVfbWV0YWRhdGEgfD4KICBkcGx5cjo6ZmlsdGVyKHNjcGNhX2xpYnJhcnlfaWQgJWluJSBkaXNhZ3JlZV9pZHMpCmBgYAoKTG9va2luZyBjbG9zZWx5IGhlcmUsIHdlIGNhbiBzZWUgdGhhdCB0aGVzZSBzYW1wbGVzIHdlcmUgaW4gZmFjdCBkZXJpdmVkIGZyb20gdHdvIHBhcnRpY2lwYW50cyB3aGVyZSBvbmUgc2FtcGxlIGlzIGEgeGVub2dyYWZ0IGFuZCB0aGUgb3RoZXIgaXMgdGlzc3VlLgoKYGBge3J9CmRpc2FncmVlX21ldGFkYXRhIHw+CiAgZHBseXI6OnNlbGVjdChzY3BjYV9saWJyYXJ5X2lkLCBzYW1wbGVfdHlwZSwgcGFydGljaXBhbnRfaWQpIHw+CiAgZHBseXI6OmFycmFuZ2UocGFydGljaXBhbnRfaWQpCmBgYAoKVGhlc2UgcGFydGljaXBhbnQgSURzIGFyZSBub3QgYXNzb2NpYXRlZCB3aXRoIGFueSBvdGhlciBzYW1wbGVzOgoKYGBge3J9CnNhbXBsZV9tZXRhZGF0YSB8PgogIGRwbHlyOjpmaWx0ZXIocGFydGljaXBhbnRfaWQgJWluJSBkaXNhZ3JlZV9tZXRhZGF0YSRwYXJ0aWNpcGFudF9pZCkgfD4KICBkcGx5cjo6ZmlsdGVyKCEoc2NwY2FfbGlicmFyeV9pZCkgJWluJSBkaXNhZ3JlZV9pZHMpCmBgYAoKSXQncyBwb3NzaWJsZSB0aGF0IHBhdGllbnQtc3BlY2lmaWMgYmlvbG9neSBleHBsYWlucyB0aGUgZGlzY3JlcGFuY2llcyB3ZSBzZWUgZm9yIHRoZXNlIHNhbXBsZXMnIGNlbGwgdHlwZXMuCgpXZSdsbCBsb29rIG1vcmUgY2xvc2VseSBhdCB0aGVpciBkaXNhZ3JlZW1lbnRzIGFzIHdlbGwgd2l0aCBoZWF0bWFwczoKCmBgYHtyLCBmaWcuaGVpZ2h0ID0gOCwgZmlnLndpZHRoID0gOH0gCmRpc2FncmVlX2lkcyB8PgogICMgd2FsayB0byBvbmx5IHByaW50IHRoZSBoZWF0bWFwcyBvbmNlCiAgcHVycnI6OndhbGsoCiAgICBcKGlkKSB7CiAgICAgIGxpYnJhcnlfZGYgPC0gY2VsbHR5cGVfd2lkZV9kZiB8PgogICAgICAgIGRwbHlyOjpmaWx0ZXIobGlicmFyeV9pZCA9PSBpZCkKICAgICAgCiAgICAgZnJhY19kaXNhZ3JlZSA8LSBmcmFjX2RmIHw+CiAgICAgICBkcGx5cjo6ZmlsdGVyKGxpYnJhcnlfaWQgPT0gaWQpIHw+CiAgICAgICBkcGx5cjo6cHVsbChmcmFjX2Rpc2FncmVlKQogICAgICAgCiAgICAgCiAgICAgaGVhdG1hcF90aXRsZSA8LSBnbHVlOjpnbHVlKCJ7aWR9IChmcmFjdGlvbiBkaXNhZ3JlZSA9IHtmcmFjX2Rpc2FncmVlfSkiKQogICAgICAKICAgICAgbWFrZV9qYWNjYXJkX2hlYXRtYXAoCiAgICAgICAgbGlicmFyeV9kZiwKICAgICAgICAic2NhbnZpIiwKICAgICAgICAic2luZ2xlciIsCiAgICAgICAgZ2x1ZTo6Z2x1ZSgie2hlYXRtYXBfdGl0bGV9XG5cbnNjQU5WSSBsYWJlbCIpLAogICAgICAgICJTaW5nbGVSIGxhYmVsIgogICAgICApCiAgfSkKYGBgCgoKRm9yIHRoZXNlIGxpYnJhcmllcywgdGhlIG1haW4gc291cmNlIG9mIGRpc2FncmVlbWVudCBpcyBgc2NBTlZJYCBhcHBseWluZyBtb3JlIGhvbW9nZW5lb3VzIGxhYmVsaW5nIG9mIGBOZXVyb2VuZG9jcmluZWAgY29tcGFyZWQgdG8gYFNpbmdsZVJgIHdoaWNoIGlzIGFzc2lnbmluZyBtb3JlIHZhcmllZCBjZWxsIHR5cGVzLgpOb3RlIHRoYXQgdGhlIHR3byBsaWJyYXJpZXMgd2hlcmUgYHNjQU5WSWAgaXMgX29ubHlfIGRldGVjdGluZyBgTmV1cm9lbmRvY3JpbmVgIGNlbGxzIGFyZSB0aGUgUERYIGxpYnJhcmllcywgZm9yIHdoaWNoIHdlIGV4cGVjdCBsb3dlciBjZWxsIHR5cGUgZGl2ZXJzaXR5IGluIGdlbmVyYWwuCkl0J3Mgbm90IHVubGlrZWx5IHRoYXQgYSBsb3Qgb2Ygd2hhdCBgU2luZ2xlUmAgaGFzIGxhYmVsZWQgYFN0cm9tYWwgb3RoZXJgIG9yIGBTY2h3YW5uYCBtYXkgYmUgdHVtb3IuCgoKIyMgQ29uY2x1c2lvbnMKCk92ZXJhbGwgYFNpbmdsZVJgIGFuZCBgc2NBTlZJYCBhcHBlYXIgdG8gaGF2ZSBtYWRlIGhpZ2hseSBzaW1pbGFyIHByZWRpY3Rpb25zIGZvciB0aGUgdmFzdCBtYWpvcml0eSBvZiBsaWJyYXJpZXMuClRoZXNlIGNlbGwgdHlwZXMgYXJlIGFsc28gZmFpcmx5IHNpbWlsYXIgdG8gb3VyIGV4aXN0aW5nIGNvbnNlbnN1cyBjZWxsIHR5cGVzLgpUaGVyZWZvcmUsIHdlIGNhbiB1c2UgYWdyZWVtZW50IGJldHdlZW4gYFNpbmdsZVJgIGFuZCBgc2NBTlZJYCB0byBkZXJpdmUgZmluYWwgYW5ub3RhdGlvbnMgZm9yIHRoaXMgYW5hbHlzaXMgbW9kdWxlLgoKCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAo=